#include "assert.h"
#include "error.h"
#include "stddef.h"
#include "stdio.h"
#include "string.h"
#include "proc.h"
#include "mmu.h"
#include "dbg.h"
#include "lock.h"
#include "trap.h"
#include "console.h"

#include "fs/deffs.h"
#include "fs/file.h"
#include "fs/fs.h"
#include "fs/vfs.h"
#include "fs/inode.h"
#include "fs/iobuf.h"
#include "fs/stat.h"
#include "fs/iobuf.h"
#include "fs/dirent.h"

#include "fs/sysfile.h"

#define IOBUF_SIZE 4096

// static semaphore_t file_lk;
static spinlock_t file_lk;

static inline void
sysfile_lock(void) {
    spinlock_acquire(&file_lk);
}

static inline void
sysfile_unlock(void) {
    spinlock_release(&file_lk);
}

static int
copy_path(char** to, const char* from) {
    *to = kmalloc(strlen(from) + 1);
    if (*to == NULL)
        return -E_NO_MEM;
    strcpy(*to, from);
    return 0;
}

void
sysfile_init() {  // TODO
    spinlock_init(&file_lk, "sysfile_lk");
}

static dirent_t*
sysfile_testfd(const char* __path) {
    char* path;
    if (copy_path(&path, __path) < 0)
        return NULL;
    char name[FAT32_MAX_FILENAME + 1];
    dirent_t* dentry = __fat32_lookupPath(path, FALSE, name);
    kmfree(path);
    return dentry;
}

dirent_t*
__sysfile_create(char* path, uint32_t type, uint32_t mode) {  // TODO mode有问题
    struct dirent_t *tentry, *dentry;
    char name[FAT32_MAX_FILENAME + 1];
    dentry = __fat32_nameParent(path, name);
    kmfree(path);
    if (dentry == NULL)
        return NULL;

    if (type == S_IFDIR)
        mode = ATTR_DIRECTORY;
    else if (mode & O_RDONLY)
        mode = ATTR_READ_ONLY;
    else
        mode = 0;

    dentry_lock(dentry);
    if ((tentry = __fat32_alloc(dentry, name, mode)) == NULL) {
        dentry_unlock(dentry);
        __fat32_put(dentry);
        return NULL;
    }

    if ((type == S_IFDIR && !(tentry->d_type & ATTR_DIRECTORY)) || (type == S_IFREG && (tentry->d_type & ATTR_DIRECTORY))) {
        dentry_unlock(dentry);
        __fat32_put(tentry);
        __fat32_put(dentry);
        return NULL;
    }
    dentry_unlock(dentry);
    __fat32_put(dentry);

    dentry_unlock(tentry);
    return tentry;
}

/*
int
sysfile_open(const char* __path, uint32_t flags) {
    char* path;
    int ret = copy_path(&path, __path);
    if (ret < 0)
        return ret;
    ret = file_open(path, flags);
    kmfree(path);
    return ret;
}

int
sysfile_close(int fd) {
    return file_close(fd);
}

int
sysfile_read(int fd, void* base, size_t len) {  // TODO
    if (len == 0)
        return 0;
    if (!file_testfd(fd, 1, 0))
        return -E_INVAL;

    int ret = 0;
    size_t copied = 0, _len;
    while (len != 0) {
        _len = __MIN(len, IOBUF_SIZE);
        ret = file_read(fd, base, _len, &_len);
        if (_len != 0) {
            // sysfile_lock();
            // intr_off();  // TODO
            {
                // TODO lock
                //  if (copy_to_user(mm, base, buffer, alen)) {
                base += _len;
                len -= _len;
                copied += _len;
                // }
            }
            // sysfile_unlock();
            // intr_on();
        }
        if (ret < 0 || _len == 0)
            goto over;
    }
over:
    if (copied != 0)
        return copied;
    else
        return ret;
}

int
sysfile_write(int fd, void* base, size_t len) {  // TODO
    if (len == 0)
        return 0;
    if (!file_testfd(fd, 0, 1))
        return -E_INVAL;

    int ret = 0;
    size_t copied = 0, _len;
    while (len != 0) {
        _len = __MIN(len, IOBUF_SIZE);

        if (ret >= 0) {
            ret = file_write(fd, base, _len, &_len);
            if (_len != 0) {
                base += _len;
                len -= _len;
                copied += _len;
            }
        }
        if (ret < 0 || _len == 0)
            goto over;
    }
over:
    if (copied != 0)
        return copied;
    else
        return ret;
}

int
sysfile_seek(int fd, offset_t pos, int startPos) {
    return file_seek(fd, pos, startPos);
}

int
sysfile_fstat(int fd, stat_t* __stat) {
    return file_fstat(fd, __stat);
}

int
sysfile_fsync(int fd) {
    return file_fsync(fd);
}
*/
int
sysfile_open(int fd, const char* __path, uint32_t flags) {
    spinlock_acquire(&getCurrentProc()->lk);

    char* path;
    dirent_t* dentry;
    dirent_t* oldCwd = getCurrentProc()->cwd;
    int ret;
    copy_path(&path, __path);
    if (path == NULL)
        return -E_NO_MEM;
    if (fd >= 0) {
        file_t* f = getCurrentProc()->openFiles[fd];
        if (f == NULL || f->openCount == 0) {
            ret = -E_INVAL;
            goto out;
        }
        getCurrentProc()->cwd = f->d_entry;
    }
    if (flags & O_CREATE) {  //新建文件
        dentry = __sysfile_create(path, S_IFREG, flags);
        if (dentry == NULL) {
            ret = -E_NOENTRY;
            goto out;
        }
    } else {
        dentry = __fat32_name(path);
        if (dentry == NULL) {
            ret = -E_NOENTRY;
            goto out;
        }
        dentry_lock(dentry);
        if ((dentry->d_type & ATTR_DIRECTORY) && (flags != O_DIRECTORY)) {
            dentry_unlock(dentry);
            __fat32_put(dentry);
            ret = -E_INVAL;
            goto out;
        }
    }
    file_t* f = __file_alloc();
    if (f == NULL) {
        ret = -1;
        goto out;
    }
    int __fd = __fd_alloc(f);
    if (__fd < 0) {
        __file_close(f);
        ret = -1;
        goto out;
    }

    if (!(dentry->d_type & ATTR_DIRECTORY) && (flags & O_TRUNC))
        __fat32_trunc(dentry);

    f->type = FD_ENTRY;
    f->pos = (flags & O_APPEND) ? dentry->size : 0;
    f->d_entry = dentry;
    f->readable = (flags == O_RDONLY) || ((flags & O_RDWR) && (flags != O_WRONLY));
    f->writeable = flags & O_WRONLY;
    dentry_unlock(dentry);

    kmfree(path);
    if (fd >= 0) {
        getCurrentProc()->cwd = oldCwd;
        spinlock_release(&getCurrentProc()->lk);
    }
    return __fd;

out:
    if (fd >= 0) {
        getCurrentProc()->cwd = oldCwd;
        spinlock_release(&getCurrentProc()->lk);
    }
    kmfree(path);
    return ret;
}

int
sysfile_openat(int dirfd, const char* pathname, int flags) {}

int
sysfile_close(int fd) {
    file_t* f = getCurrentProc()->openFiles[fd];
    getCurrentProc()->openFiles[fd] = NULL;
    return __file_close(f);
}

int
sysfile_read(int fd, void* dst, size_t len) {
    file_t* f = getCurrentProc()->openFiles[fd];
    if (f == NULL || f->openCount == 0)
        return -E_INVAL;
    dirent_t* dentry = f->d_entry;

    dentry_lock(dentry);
    int ret = __fat32_read(dentry, 0, dst, f->pos, &len);
    f->pos += len;
    dentry_unlock(dentry);

    return ret;
}

int
sysfile_write(int fd, void* src, size_t len) {
    file_t* f = getCurrentProc()->openFiles[fd];
    int ret;
    if (f == STDOUT_PTR) {
        console_write(src, 0, len);
        ret = 0;
    } else if (f == NULL || f->openCount == 0)
        return -E_INVAL;
    else {
        dirent_t* dentry = f->d_entry;
        dentry_lock(dentry);
        ret = __fat32_write(dentry, 0, src, f->pos, &len);
        f->pos += len;
        dentry_unlock(dentry);
    }

    return ret;  // TODO
}

int
sysfile_seek(int fd, offset_t pos, int startPos) {
    // return -E_UNIMP;  // TODO

    file_t* f = getCurrentProc()->openFiles[fd];
    if (f == NULL || f->openCount == 0)
        return -E_INVAL;
    dirent_t* dentry = f->d_entry;

    stat_t stat;
    switch (startPos) {
        case LSEEK_SET:
            break;
        case LSEEK_CUR:
            pos += f->pos;
            break;
        case LSEEK_END: {
            __fat32_fstat(dentry, &stat);
            pos = dentry->size;
        } break;
        default:
            break;
    }
    f->pos = pos;
    return 0;
}

int
sysfile_fstat(int fd, stat_t* stat) {
    file_t* f = getCurrentProc()->openFiles[fd];
    if (f == NULL || f->openCount == 0)
        return -E_INVAL;
    dirent_t* dentry = f->d_entry;
    dentry_lock(dentry);
    int ret = __fat32_fstat(dentry, stat);
    dentry_unlock(dentry);
    return ret;
}

int
sysfile_fsync(int fd) {
    file_t* f = getCurrentProc()->openFiles[fd];
    if (f == NULL || f->openCount == 0)
        return -E_INVAL;
    dirent_t* dentry = f->d_entry;
    dentry_lock(dentry);
    int ret = __fat32_fsync(dentry);
    dentry_unlock(dentry);
    return ret;
}

int
sysfile_chdir(const char* __path) {
    dirent_t* dentry = sysfile_testfd(__path);
    if (dentry == NULL)
        return -E_NO_DEV;
    dirent_t* cwd = getCurrentProc()->cwd;
    if (!(dentry->d_type & ATTR_DIRECTORY))
        return -1;  // TODO
    __fat32_put(cwd);
    getCurrentProc()->cwd = dentry;
    return 0;
}

int
sysfile_mkdir(const char* __path, uint32_t mode) {
    char* path;
    copy_path(&path, __path);
    dirent_t* tentry = __sysfile_create(path, S_IFDIR, mode);
    kmfree(path);
    if (tentry == NULL)
        return -1;
    __fat32_put(tentry);
    return 0;
}

int
sysfile_mkdirat(int dirfd, const char* path, uint32_t mode) {
    // TODO
}

int
sysfile_link(const char* __path1, const char* __path2) {
    return -E_UNIMP;  // TODO

    char *oldPath, *newPath;
    int ret = copy_path(&oldPath, __path1);
    if (ret < 0)
        return ret;
    ret = copy_path(&newPath, __path2);
    if (ret < 0) {
        kmfree(oldPath);
        return ret;
    }
    ret = vfs_link(oldPath, newPath);
    kmfree(newPath);
    kmfree(oldPath);
    return ret;
}

static int
isDirEmpty(dirent_t* dir) {
    dirent_t temp;
    int count;  // TODO占位
    temp.valid = FALSE;
    return __fat32_enExt(dir, &temp, 2 * sizeof(fat32Entry_t), &count) < 0;
}

int
sysfile_rename(const char* __path1, const char* __path2) {  // TODO实现逻辑？
    char *oldPath, *newPath;
    int ret = copy_path(&oldPath, __path1);
    if (ret < 0)
        return ret;
    ret = copy_path(&newPath, __path2);
    if (ret < 0) {
        kmfree(oldPath);
        return ret;
    }
    char* name = formatName(oldPath);
    dirent_t *src, *dst = NULL, *psrc, *pdst;
    src = __fat32_name(oldPath);
    pdst = __fat32_nameParent(newPath, oldPath);
    if (src == NULL || pdst == NULL || name == NULL) {
        ret = -E_INVAL;
        goto out;
    }
    psrc = src->parent;

    uint32_t off;
    dentry_lock(src);
    dentry_lock(pdst);

    dst = __fat32_lookupDir(pdst, name, &off);
    if (dst != NULL) {
        dentry_lock(dst);
        if (src == dst)
            goto out;
        else if ((src->d_type & ATTR_DIRECTORY) && (dst->d_type & ATTR_DIRECTORY)) {
            if (!isDirEmpty(dst))
                goto out;
            else
                __fat32_remove(dst);
        } else
            goto out;
    }

    memmove(src->d_name, name, FAT32_MAX_FILENAME);
    __fat32_create(pdst, src, off);
    __fat32_remove(src);
    src->parent = __fat32_dup(pdst);
    src->d_off = off;
    src->valid = TRUE;
    __fat32_put(psrc);

out:

    if (dst != NULL) {
        __fat32_put(dst);
        dentry_unlock(dst);
    }
    __fat32_put(pdst);
    dentry_unlock(pdst);
    __fat32_put(src);
    dentry_unlock(src);

    kmfree(newPath);
    kmfree(oldPath);
    return ret;  // TODO
}

char*
sysfile_getcwd(char* buf, size_t len) {
    // int ret;
    dirent_t* dentry = getCurrentProc()->cwd;
    char path[FAT32_MAX_PATH];
    char* s;
    if (dentry->parent == NULL) {
        s = "/";
    } else {
        s = path + FAT32_MAX_PATH - 1;
        *s = '\0';
        while (dentry->parent) {
            len = strlen(dentry->d_name);
            s -= len;
            if (s <= path)  // can't reach root "/"
                return NULL;
            strncpy(buf, dentry->d_name, len);
            --s;
            *s = '/';
            dentry = dentry->parent;
        }
    }
    memcpy(buf, s, strlen(s) + 1);
    return dentry->d_name;
}

int
sysfile_getDirEntry(int fd, dirent_t* __direntp) {  // TODO?
    file_t* f = getCurrentProc()->openFiles[fd];
    if (f == NULL || f->openCount == 0)
        return -E_INVAL;
    dirent_t* dentry = f->d_entry;
    memcpy(__direntp, dentry, sizeof(dirent_t));

    return sizeof(dirent_t);
}

int
sysfile_dup(int fd) {
    spinlock_acquire(&getCurrentProc()->lk);
    file_t* f = getCurrentProc()->openFiles[fd];
    if (f == NULL)
        goto fail;
    int fdNew = __fd_alloc(f);
    if (fdNew < 0)
        goto fail;
    if (f != STDOUT_PTR && f != STDIN_PTR && f != STDERR_PTR) {
        if (f->openCount == 0)  // TODO 和fdNew的逻辑顺序不对
            goto fail;
        ++f->openCount;
    }
    spinlock_release(&getCurrentProc()->lk);
    return fdNew;

fail:
    spinlock_release(&getCurrentProc()->lk);
    return -E_INVAL;
}

int
sysfile_dup2(int fd_old, int fd_new) {
    spinlock_acquire(&getCurrentProc()->lk);

    file_t* f = getCurrentProc()->openFiles[fd_old];
    if (f == NULL)  //|| f->openCount == 0
        goto fail;
    if (fd_new >= N_OPEN_FILE || getCurrentProc()->openFiles[fd_new] != NULL)
        goto fail;
    getCurrentProc()->openFiles[fd_new] = f;
    if (f != STDOUT_PTR && f != STDIN_PTR && f != STDERR_PTR)
        ++f->openCount;
    spinlock_release(&getCurrentProc()->lk);
    return fd_new;

fail:
    spinlock_release(&getCurrentProc()->lk);
    return -E_INVAL;
}

int
sysfile_pipe(int* fd_store) {
    return -E_UNIMP;  // TODO
}

int
sysfile_mkfifo(const char* __name, uint32_t flags) {
    return -E_UNIMP;
}

int
sys_mount(const char* dev, const char* dir, const char* fsType, uint32_t flags, const void* data) {
    return -E_UNIMP;  // TODO
}

int
sys_unmount(const char* dev, uint32_t flags) {
    return -E_UNIMP;  // TODO
}